const EVENT_MAP = {
  text: 'input',
  password: 'input',
  number: 'input',
  radio: 'click',
  checkbox: 'click',
  textarea: 'input',
  'select-one': 'change',
  submit: 'click'
};

class FormCheck {
  constructor(
    el,
    className,
    { formData, validators, pass, noPass, handleSubmit }
  ) {
    //获取所有input
    this.oFormElements = document.querySelector(el).querySelectorAll(className);
    this.formData = this.proxyFormData(formData);
    //一个对象保存许多校验方法
    this.validators = validators;
    this.pass = pass;
    this.noPass = noPass;
    this.handleSubmit = handleSubmit;
    //该容器保存每个type对应规则校验后的结果
    this.result = {};
    this.init(formData);
  }

  static create(
    el,
    className,
    { formData, validators, pass, noPass, handleSubmit }
  ) {
    new FormCheck(el, className, {
      formData,
      validators,
      pass,
      noPass,
      handleSubmit
    });
  }

  /**
   * 将用户交互的数据保存至formData里
   * @param {*} el 发生更改的input节点
   */
  setValue(el) {
    const { type, name, value } = el;
    switch (type) {
      //button submit没有name, value
      case 'submit':
        this.handleSubmitClick();
        break;
      case 'checkbox':
        //此操作将点击的hobbie名称加入数组保存，再次点击就剔除该项
        // console.log(name, value); hobbies music/hobbies travel/...
        //如果checkbox存在相同项时，删除该项
        if (this.formData[name].includes(value)) {
          this.formData[name] = this.formData[name].filter(
            //过滤不符合的项即删除该项
            (item) => item !== value
          );
        } else {
          //不存在时合并该项
          this.formData[name] = [...this.formData[name], value];
        }
        break;
      //其他情况时，直接赋值
      default:
        this.formData[name] = value;
        break;
    }
  }

  /**
   * 提交按钮点击
   */
  handleSubmitClick() {
    //遍历提交之前把校验结果(成功或失败)集合的值组成一个数组
    const resValueArr = Object.values(this.result);
    //假如结果数组有false时，返回第一个false结果的索引
    const falseIndex = resValueArr.findIndex((item) => !item);
    //排除没有索引情况
    if (falseIndex !== -1) {
      //获取所有校验规则结果的键名
      const resKeyArr = Object.keys(this.result);
      //获取不符合校验的当前索引对应的键名
      const falseKey = resKeyArr[falseIndex];
      //获取当前不符合校验规则的错误信息
      const { msg } = this.validators[falseKey](this.formData[falseKey]);
      //执行不通过的方法(key,value,msg)
      this.noPass(falseKey, this.formData[falseKey], msg);
      return;
    }

    //克隆一份表单数据, 并将代理对象转为普通对象
    const fd = JSON.parse(JSON.stringify(this.formData));

    //提交表单，向后端请求数据
    this.handleSubmit(fd);
  }

  /**
   * 新增校验结果
   * @param {*} data 代理之前的formData数据
   */
  addResult(data) {
    for (let key in data) {
      if (this.validators[key]) {
        //各项校验结果的初始值为false
        this.result[key] = false;
      }
    }
  }

  setResult(key, bool) {
    this.result[key] = bool;
  }

  /**
   * 通过代理在修改赋值的同时进行校验
   * @param {*} data 数据
   * @returns 一个代理后的对象数据
   */
  proxyFormData(data) {
    var _self = this;
    return new Proxy(data, {
      get(obj, key) {
        return Reflect.get(obj, key);
      },
      set(obj, key, value) {
        //校验
        _self.validate(key, value);
        return Reflect.set(obj, key, value);
      }
    });
  }

  /**
   * 通过一个对象集合，底下有许多函数校验方法，按照事件单独的触发进行规则校验，符合条件时抛出需要的参数，不符合时抛出需要的参数。
   * @param {*} key 校验的规则名称，如gender,username,password
   * @param {*} value 用户交互发生填入的值
   */
  validate(key, value) {
    // console.log(this.validators);
    //{gender: null, realname: ƒ, age: ƒ, username: ƒ, password: ƒ, …}
    const keyValidator = this.validators[key];
    //排除gender是null的情况
    if (this.validators[key]) {
      const { reg, msg } = keyValidator(value);
      //校验不符合条件时
      if (!reg) {
        this.setResult(key, false);
        this.noPass(key, value, msg);
        return;
      }
      this.setResult(key, true);
      this.pass(key, value, msg);
    }
  }

  bindEvent() {
    this.oFormElements.forEach((el) => {
      // console.log(el.type);
      //text number password radio select-one submit textarea
      el.addEventListener(
        EVENT_MAP[el.type],
        this.setValue.bind(this, el),
        false
      );
    });
  }

  init(fd) {
    this.addResult(fd);
    this.bindEvent();
  }
}

export default FormCheck;
